package com.lcf.redis.utils;

import com.lcf.base.exception.ExceptionEnum;
import com.lcf.base.exception.SysException;
import com.lcf.redis.enums.RedisLockPrefixEnum;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * 分布式锁
 *
 * @author lcf
 */
@Component
public class RedisLockUtils {
    private final RedissonClient redissonClient;

    @Autowired
    public RedisLockUtils(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    /**
     * 获取锁
     */
    public void tryLock(RedisLockPrefixEnum prefixEnum,
                        Object key, Runnable run) {
        tryLock(prefixEnum, key,
                new SysException(ExceptionEnum.ERROR),
                run);
    }

    /**
     * 获取锁
     */
    public <T> T tryLock(RedisLockPrefixEnum prefixEnum,
                         Object key, Supplier<T> supplier) {
        return tryLock(prefixEnum, key,
                new SysException(ExceptionEnum.ERROR),
                supplier);
    }

    /**
     * 获取锁
     */
    public void tryLock(RedisLockPrefixEnum prefixEnum,
                        Object key, ExceptionEnum exceptionEnum,
                        Runnable run) {
        tryLock(prefixEnum, key, new SysException(exceptionEnum), run);
    }

    /**
     * 获取锁
     */
    public <T> T tryLock(RedisLockPrefixEnum prefixEnum,
                         Object key, ExceptionEnum exceptionEnum,
                         Supplier<T> supplier) {
        return tryLock(prefixEnum, key, new SysException(exceptionEnum), supplier);
    }

    /**
     * 获取锁
     */
    public <E extends RuntimeException> void tryLock(RedisLockPrefixEnum prefixEnum,
                                                     Object key, E e,
                                                     Runnable run) {
        if (!tryLock(prefixEnum, key)) {
            throw e;
        }

        try {
            run.run();
        } finally {
            unLock(prefixEnum, key);
        }
    }

    /**
     * 获取锁
     */
    public <E extends RuntimeException, T> T tryLock(RedisLockPrefixEnum prefixEnum,
                                                     Object key, E e,
                                                     Supplier<T> supplier) {
        if (!tryLock(prefixEnum, key)) {
            throw e;
        }

        try {
            return supplier.get();
        } finally {
            unLock(prefixEnum, key);
        }
    }

    /**
     * 获取锁
     */
    public void lock(RedisLockPrefixEnum prefixEnum,
                     Object key,
                     Runnable run) {
        lock(prefixEnum, key);

        try {
            run.run();
        } finally {
            unLock(prefixEnum, key);
        }
    }

    /**
     * 获取锁
     */
    public <T> T lock(RedisLockPrefixEnum prefixEnum,
                      Object key,
                      Supplier<T> supplier) {
        lock(prefixEnum, key);

        try {
            return supplier.get();
        } finally {
            unLock(prefixEnum, key);
        }
    }

    /**
     * 获取锁
     */
    public void lock(RedisLockPrefixEnum prefixEnum,
                     Object key, long time, TimeUnit unit,
                     Runnable run) {
        lock(prefixEnum, key, time, unit);

        try {
            run.run();
        } finally {
            unLock(prefixEnum, key);
        }
    }

    /**
     * 获取锁
     */
    public <T> T lock(RedisLockPrefixEnum prefixEnum,
                      Object key, long time, TimeUnit unit,
                      Supplier<T> supplier) {
        lock(prefixEnum, key, time, unit);

        try {
            return supplier.get();
        } finally {
            unLock(prefixEnum, key);
        }
    }

    /**
     * 获取锁
     */
    public void lock(RedisLockPrefixEnum prefixEnum, Object key) {
        redissonClient.getLock(getKey(prefixEnum, key)).lock();
    }

    /**
     * 获取锁
     */
    public void lock(RedisLockPrefixEnum prefixEnum, Object key, long time, TimeUnit unit) {
        redissonClient.getLock(getKey(prefixEnum, key)).lock(time, unit);
    }

    /**
     * 获取锁
     */
    public boolean tryLock(RedisLockPrefixEnum prefixEnum, Object key) {
        return redissonClient.getLock(getKey(prefixEnum, key)).tryLock();
    }

    /**
     * 获取锁
     */
    public boolean tryLock(RedisLockPrefixEnum prefixEnum, Object key, long time, TimeUnit unit) throws InterruptedException {
        return redissonClient.getLock(getKey(prefixEnum, key)).tryLock(time, unit);
    }

    /**
     * 释放锁
     */
    public void unLock(RedisLockPrefixEnum prefixEnum, Object key) {
        RLock lock = redissonClient.getLock(getKey(prefixEnum, key));
        if (lock != null && lock.isLocked() && lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }

    /**
     * 是否存在
     */
    public boolean hasKey(RedisLockPrefixEnum prefixEnum, Object key) {
        return redissonClient.getLock(getKey(prefixEnum, key)).isLocked();
    }

    /**
     * 组装key
     */
    private String getKey(RedisLockPrefixEnum prefixEnum, Object key) {
        return "lock:" + prefixEnum.getName() + ":" + key;
    }
}
